/*
Filename:    sctp_connection.cpp
Author:      Erik Öjebo
Date:        2009-01-10
Description: Represents an SCTP one-to-one connection.
Notes:       Should not be instantiated (an cannot be). Use
             SctpOneToManyConnection or SctpOneToOneConnection instead.
*/

// See header file for documentation.

#include "sctp_connection.h"
#include <stdexcept>
#include <cerrno>
#include <iostream>

#define BACKLOG 5

void SctpConnection::initialize()
{
    // Initialize the mutex that protects the AssociationCollection:
    pthread_mutex_init(&assocMutex, NULL);
}

void SctpConnection::startListen()
{
    if ( listen(socketFd, BACKLOG) < 0)
    {
        throw std::runtime_error(
                "SctpOneToManyListeningConnection: error in listen");
    }
}

void SctpConnection::close()
{
    // Close the socket file descriptor:
    ::close(socketFd);
}

sctp_assoc_t SctpConnection::
    getAssociationId(const Address& address) const
{
    struct sctp_paddrparams parameters;
    socklen_t parametersSize;

    parametersSize = sizeof(parameters);

    // Clear the parameters struct and copy the socket address to it:
    memset(&parameters, '\0', parametersSize);
    memcpy(&parameters.spp_address, address.getSocketAddress(),
            address.getSocketAddressSize());

    // Get the info about the given address:
    if ( sctp_opt_info(socketFd, 0, SCTP_GET_PEER_ADDR_INFO,
            &parameters, &parametersSize) < 0)
    {
        perror("sctp_opt_info");
        throw std::runtime_error(
                "SctpConnection: could not get association id");
    }

    // Return the association id:
    return parameters.spp_assoc_id;
}

int SctpConnection::getAssociationCount()
{
    // The association collection is not thread safe, so get the mutex
    // before reading from it:
    pthread_mutex_lock(&assocMutex);
    int associationCount = associations.count();
    pthread_mutex_unlock(&assocMutex);

    return associationCount;
}

ssize_t SctpConnection::send(void const * const buffer,
       size_t size, const sctp_assoc_t assocId, unsigned int streamNumber) const
{
    struct sctp_sndrcvinfo sri;
    ssize_t sentBytes;

    // Initialize send/receive information (association id and stream number):
    memset(&sri, '\0', sizeof(sri));
    sri.sinfo_assoc_id = assocId;
    sri.sinfo_stream = streamNumber;

    // Send the message:
    sentBytes = sctp_send(socketFd, buffer, size, &sri, 0);

    return sentBytes;
}

SctpReceiveResult SctpConnection::receive(void* buffer, size_t size)
{
    ssize_t readBytes;
    int messageFlags = 0;
    bool wasNotification;
    struct sockaddr_in peerAddress;
    size_t peerAddressSize = sizeof(peerAddress);
    struct sctp_sndrcvinfo sri;
    SctpReceiveResult result;

    // Clear the sndrcvinfo structure
    memset(&sri, '\0', sizeof(sri));

    // Receive a message:
    readBytes = sctp_recvmsg(socketFd, buffer, size,
            (struct sockaddr*)&peerAddress, &peerAddressSize,
            &sri, &messageFlags);

    if (readBytes < 0)
        result.setErrorNumber(errno);

    wasNotification = messageFlags & MSG_NOTIFICATION;

    // Is the received message a notification? If so, handle silently.
    if (wasNotification)
        handleNotification(buffer);

    // Set the return value properties:
    result.setPeerAddress(Address(&peerAddress));
    result.setAssociationId(sri.sinfo_assoc_id);
    result.setMessageSize(readBytes);
    result.setWasNotification(wasNotification);

    return result;
}

SctpReceiveResult SctpConnection::
    receiveMessage(void* buffer, size_t size)
{
    SctpReceiveResult result;

    // Call receive until a message from the client is received.
    // All notifications are handled silently.
    do
    {
        // Receive a message:
        result = receive(buffer, size);

    } while(result.wasNotification());

    return result;
}

void SctpConnection::handleNotification(void* buffer)
{
    union sctp_notification* notification;

    notification = (sctp_notification*)buffer;

    // The association collection is not thread safe, so get the mutex
    // before using it:
    pthread_mutex_lock(&assocMutex);

    switch(notification->sn_header.sn_type)
    {
    case SCTP_ASSOC_CHANGE:
        // Handle the event to keep the association collection up to date:
        associations.handleAssociationEvent(notification);
        break;
    }

    pthread_mutex_unlock(&assocMutex);
}

void SctpConnection::bind(const std::vector<Address>& addresses)
{
    bind(addresses, SCTP_BINDX_ADD_ADDR);
}

void SctpConnection::unbind(const std::vector<Address>& addresses)
{
    bind(addresses, SCTP_BINDX_REM_ADDR);
}

void SctpConnection::bind(const Address& address)
{
    // Explicitly call global level bind to avoid conflicts with class level
    // bind method.
    if (::bind(socketFd,(struct sockaddr*)address.getSocketAddress(),
            address.getSocketAddressSize()) < 0)
    {
        throw std::runtime_error(
                "SctpConnection: could not bind socket");
    }
}

void SctpConnection::bindToAll(int portNumber)
{
    std::vector<Address> addresses;

    // Get list of all available Addresses
    Address::getAllAvailable(addresses, portNumber);

    bind(addresses);
}

void SctpConnection::bind(
        const std::vector<Address>& addresses, int flags)
{
    struct sockaddr_in *socketAddresses, *socketAddress;

    // Create an array to hold all the sockaddr_in structures for the
    // addresses. Needed for the sctp_bindx call.
    int addressCount = addresses.size();
    socketAddresses = new sockaddr_in[addressCount];

    // Add all the addresses' sockaddr_in structures to the array:
    for (int i = 0; i < addressCount; i++)
    {
        socketAddress = (struct sockaddr_in*)addresses[i].getSocketAddress();
        socketAddresses[i] = *socketAddress;
    }

    // Bind all the addresses given by the caller:
    if (sctp_bindx(socketFd, (struct sockaddr*)socketAddresses,
            addressCount, flags) < 0)
    {
        perror("bindx");
        throw std::runtime_error(
                "SctpConnection: sctp_bindx error");
    }

    // Free allocated memory:
    delete socketAddresses;
}

void SctpConnection::getBoundAddresses(std::vector<Address>& addresses)
{
    struct sockaddr_in * addrs;
    int addrCount;

    addrCount = sctp_getladdrs(socketFd, 0, (struct sockaddr**)&addrs);

    // If an error occurred during the getladdrs call, just return without
    // adding any addresses to the vector.
    // No clean up is needed since sctp_getladdrs has not allocated any
    // memory for the result.
    if (addrCount < 0)
        return;

    // For each address returned from getladdrs, create an Address instance
    // from the sockaddr_in pointer:
    for (int i = 0; i < addrCount; i++)
        addresses.push_back(Address(addrs+i));

    // Free the memory allocated by sctp_getladdrs:
    sctp_freeladdrs((struct sockaddr*)addrs);
}
